/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.handmark.pulltorefresh.library;

import me.himi.love.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
import com.handmark.pulltorefresh.library.internal.IndicatorLayout;

public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements OnScrollListener {

    private static FrameLayout.LayoutParams convertEmptyViewLayoutParams(ViewGroup.LayoutParams lp) {
	FrameLayout.LayoutParams newLp = null;

	if (null != lp) {
	    newLp = new FrameLayout.LayoutParams(lp);

	    if (lp instanceof LinearLayout.LayoutParams) {
		newLp.gravity = ((LinearLayout.LayoutParams) lp).gravity;
	    } else {
		newLp.gravity = Gravity.CENTER;
	    }
	}

	return newLp;
    }

    private boolean mLastItemVisible;
    private OnScrollListener mOnScrollListener;
    private OnLastItemVisibleListener mOnLastItemVisibleListener;
    private View mEmptyView;

    private IndicatorLayout mIndicatorIvTop;
    private IndicatorLayout mIndicatorIvBottom;

    private boolean mShowIndicator;
    private boolean mScrollEmptyView = true;

    public PullToRefreshAdapterViewBase(Context context) {
	super(context);
	mRefreshableView.setOnScrollListener(this);
    }

    public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
	super(context, attrs);
	mRefreshableView.setOnScrollListener(this);
    }

    public PullToRefreshAdapterViewBase(Context context, Mode mode) {
	super(context, mode);
	mRefreshableView.setOnScrollListener(this);
    }

    public PullToRefreshAdapterViewBase(Context context, Mode mode, AnimationStyle animStyle) {
	super(context, mode, animStyle);
	mRefreshableView.setOnScrollListener(this);
    }

    /**
     * Gets whether an indicator graphic should be displayed when the View is in
     * a state where a Pull-to-Refresh can happen. An example of this state is
     * when the Adapter View is scrolled to the top and the mode is set to
     * {@link Mode#PULL_FROM_START}. The default value is <var>true</var> if
     * {@link PullToRefreshBase#isPullToRefreshOverScrollEnabled()
     * isPullToRefreshOverScrollEnabled()} returns false.
     * 
     * @return true if the indicators will be shown
     */
    public boolean getShowIndicator() {
	return mShowIndicator;
    }

    public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {

	if (DEBUG) {
	    Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount + ". Total Items:" + totalItemCount);
	}

	/**
	 * Set whether the Last Item is Visible. lastVisibleItemIndex is a
	 * zero-based index, so we minus one totalItemCount to check
	 */
	if (null != mOnLastItemVisibleListener) {
	    mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);
	}

	// If we're showing the indicator, check positions...
	if (getShowIndicatorInternal()) {
	    updateIndicatorViewsVisibility();
	}

	// Finally call OnScrollListener if we have one
	if (null != mOnScrollListener) {
	    mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
	}
    }

    public final void onScrollStateChanged(final AbsListView view, final int state) {
	/**
	 * Check that the scrolling has stopped, and that the last item is
	 * visible.
	 */
	if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {
	    mOnLastItemVisibleListener.onLastItemVisible();
	}

	if (null != mOnScrollListener) {
	    mOnScrollListener.onScrollStateChanged(view, state);
	}
    }

    /**
     * Pass-through method for {@link PullToRefreshBase#getRefreshableView()
     * getRefreshableView()}.
     * {@link AdapterView#setAdapter(android.widget.Adapter)}
     * setAdapter(adapter)}. This is just for convenience!
     * 
     * @param adapter - Adapter to set
     */
    public void setAdapter(ListAdapter adapter) {
	((AdapterView<ListAdapter>) mRefreshableView).setAdapter(adapter);
    }

    /**
     * Sets the Empty View to be used by the Adapter View.
     * <p/>
     * We need it handle it ourselves so that we can Pull-to-Refresh when the
     * Empty View is shown.
     * <p/>
     * Please note, you do <strong>not</strong> usually need to call this method
     * yourself. Calling setEmptyView on the AdapterView will automatically call
     * this method and set everything up. This includes when the Android
     * Framework automatically sets the Empty View based on it's ID.
     * 
     * @param newEmptyView - Empty View to be used
     */
    public final void setEmptyView(View newEmptyView) {
	FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();

	if (null != newEmptyView) {
	    // New view needs to be clickable so that Android recognizes it as a
	    // target for Touch Events
	    newEmptyView.setClickable(true);

	    ViewParent newEmptyViewParent = newEmptyView.getParent();
	    if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
		((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
	    }

	    // We need to convert any LayoutParams so that it works in our
	    // FrameLayout
	    FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
	    if (null != lp) {
		refreshableViewWrapper.addView(newEmptyView, lp);
	    } else {
		refreshableViewWrapper.addView(newEmptyView);
	    }
	}

	if (mRefreshableView instanceof EmptyViewMethodAccessor) {
	    ((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
	} else {
	    mRefreshableView.setEmptyView(newEmptyView);
	}
	mEmptyView = newEmptyView;
    }

    /**
     * Pass-through method for {@link PullToRefreshBase#getRefreshableView()
     * getRefreshableView()}.
     * {@link AdapterView#setOnItemClickListener(OnItemClickListener)
     * setOnItemClickListener(listener)}. This is just for convenience!
     * 
     * @param listener - OnItemClickListener to use
     */
    public void setOnItemClickListener(OnItemClickListener listener) {
	mRefreshableView.setOnItemClickListener(listener);
    }

    public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
	mOnLastItemVisibleListener = listener;
    }

    public final void setOnScrollListener(OnScrollListener listener) {
	mOnScrollListener = listener;
    }

    public final void setScrollEmptyView(boolean doScroll) {
	mScrollEmptyView = doScroll;
    }

    /**
     * Sets whether an indicator graphic should be displayed when the View is in
     * a state where a Pull-to-Refresh can happen. An example of this state is
     * when the Adapter View is scrolled to the top and the mode is set to
     * {@link Mode#PULL_FROM_START}
     * 
     * @param showIndicator - true if the indicators should be shown.
     */
    public void setShowIndicator(boolean showIndicator) {
	mShowIndicator = showIndicator;

	if (getShowIndicatorInternal()) {
	    // If we're set to Show Indicator, add/update them
	    addIndicatorViews();
	} else {
	    // If not, then remove then
	    removeIndicatorViews();
	}
    }

    ;

    @Override
    protected void onPullToRefresh() {
	super.onPullToRefresh();

	if (getShowIndicatorInternal()) {
	    switch (getCurrentMode()) {
	    case PULL_FROM_END:
		mIndicatorIvBottom.pullToRefresh();
		break;
	    case PULL_FROM_START:
		mIndicatorIvTop.pullToRefresh();
		break;
	    default:
		// NO-OP
		break;
	    }
	}
    }

    protected void onRefreshing(boolean doScroll) {
	super.onRefreshing(doScroll);

	if (getShowIndicatorInternal()) {
	    updateIndicatorViewsVisibility();
	}
    }

    @Override
    protected void onReleaseToRefresh() {
	super.onReleaseToRefresh();

	if (getShowIndicatorInternal()) {
	    switch (getCurrentMode()) {
	    case PULL_FROM_END:
		mIndicatorIvBottom.releaseToRefresh();
		break;
	    case PULL_FROM_START:
		mIndicatorIvTop.releaseToRefresh();
		break;
	    default:
		// NO-OP
		break;
	    }
	}
    }

    @Override
    protected void onReset() {
	super.onReset();

	if (getShowIndicatorInternal()) {
	    updateIndicatorViewsVisibility();
	}
    }

    @Override
    protected void handleStyledAttributes(TypedArray a) {
	// Set Show Indicator to the XML value, or default value
	mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());
    }

    protected boolean isReadyForPullStart() {
	return isFirstItemVisible();
    }

    protected boolean isReadyForPullEnd() {
	return isLastItemVisible();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
	super.onScrollChanged(l, t, oldl, oldt);
	if (null != mEmptyView && !mScrollEmptyView) {
	    mEmptyView.scrollTo(-l, -t);
	}
    }

    @Override
    protected void updateUIForMode() {
	super.updateUIForMode();

	// Check Indicator Views consistent with new Mode
	if (getShowIndicatorInternal()) {
	    addIndicatorViews();
	} else {
	    removeIndicatorViews();
	}
    }

    private void addIndicatorViews() {
	Mode mode = getMode();
	FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();

	if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {
	    // If the mode can pull down, and we don't have one set already
	    mIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);
	    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
	    params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
	    params.gravity = Gravity.TOP | Gravity.RIGHT;
	    refreshableViewWrapper.addView(mIndicatorIvTop, params);

	} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {
	    // If we can't pull down, but have a View then remove it
	    refreshableViewWrapper.removeView(mIndicatorIvTop);
	    mIndicatorIvTop = null;
	}

	if (mode.showFooterLoadingLayout() && null == mIndicatorIvBottom) {
	    // If the mode can pull down, and we don't have one set already
	    mIndicatorIvBottom = new IndicatorLayout(getContext(), Mode.PULL_FROM_END);
	    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
	    params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
	    params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
	    refreshableViewWrapper.addView(mIndicatorIvBottom, params);

	} else if (!mode.showFooterLoadingLayout() && null != mIndicatorIvBottom) {
	    // If we can't pull down, but have a View then remove it
	    refreshableViewWrapper.removeView(mIndicatorIvBottom);
	    mIndicatorIvBottom = null;
	}
    }

    private boolean getShowIndicatorInternal() {
	return mShowIndicator && isPullToRefreshEnabled();
    }

    private boolean isFirstItemVisible() {
	final Adapter adapter = mRefreshableView.getAdapter();

	if (null == adapter || adapter.isEmpty()) {
	    if (DEBUG) {
		Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
	    }
	    return true;

	} else {

	    /**
	     * This check should really just be:
	     * mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
	     * internally use a HeaderView which messes the positions up. For
	     * now we'll just add one to account for it and rely on the inner
	     * condition which checks getTop().
	     */
	    if (mRefreshableView.getFirstVisiblePosition() <= 1) {
		final View firstVisibleChild = mRefreshableView.getChildAt(0);
		if (firstVisibleChild != null) {
		    return firstVisibleChild.getTop() >= mRefreshableView.getTop();
		}
	    }
	}

	return false;
    }

    private boolean isLastItemVisible() {
	final Adapter adapter = mRefreshableView.getAdapter();

	if (null == adapter || adapter.isEmpty()) {
	    if (DEBUG) {
		Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
	    }
	    return true;
	} else {
	    final int lastItemPosition = mRefreshableView.getCount() - 1;
	    final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();

	    if (DEBUG) {
		Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: " + lastVisiblePosition);
	    }

	    /**
	     * This check should really just be: lastVisiblePosition ==
	     * lastItemPosition, but PtRListView internally uses a FooterView
	     * which messes the positions up. For me we'll just subtract one to
	     * account for it and rely on the inner condition which checks
	     * getBottom().
	     */
	    if (lastVisiblePosition >= lastItemPosition - 1) {
		final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
		final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
		if (lastVisibleChild != null) {
		    return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
		}
	    }
	}

	return false;
    }

    private void removeIndicatorViews() {
	if (null != mIndicatorIvTop) {
	    getRefreshableViewWrapper().removeView(mIndicatorIvTop);
	    mIndicatorIvTop = null;
	}

	if (null != mIndicatorIvBottom) {
	    getRefreshableViewWrapper().removeView(mIndicatorIvBottom);
	    mIndicatorIvBottom = null;
	}
    }

    private void updateIndicatorViewsVisibility() {
	if (null != mIndicatorIvTop) {
	    if (!isRefreshing() && isReadyForPullStart()) {
		if (!mIndicatorIvTop.isVisible()) {
		    mIndicatorIvTop.show();
		}
	    } else {
		if (mIndicatorIvTop.isVisible()) {
		    mIndicatorIvTop.hide();
		}
	    }
	}

	if (null != mIndicatorIvBottom) {
	    if (!isRefreshing() && isReadyForPullEnd()) {
		if (!mIndicatorIvBottom.isVisible()) {
		    mIndicatorIvBottom.show();
		}
	    } else {
		if (mIndicatorIvBottom.isVisible()) {
		    mIndicatorIvBottom.hide();
		}
	    }
	}
    }
}
